/* @flow */

import Dep from "./dep.js"
import {
    def,
    warn,
    hasOwn,
    isObject,
    isPlainObject,
    isPrimitive,
    isUndef,
    isValidArrayIndex,
    isServerRendering
} from "../util/index"

export let shouldObserve: boolean = true;
export function toggleObserving(value: boolean) {
    shouldObserve = value;
}

export class Observer {
    value: any;
    dep: Dep;
    vmCount: number;

    constructor(value: any) {
        this.value = value;
        this.dep = new Dep();
        this.vmCount = 0;
        // 新增 __ob__ 属性，用于标识该对象已经被观察
        def(value, '__ob__', this);

        // 如果 value 是对象，则进行深度观察
        if (Array.isArray(value)) {
        } else {
            this.walk(value);
        }
    }

    walk(obj: Object) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i]);
        }
    }

    /**
     * 监听列表中的数组项
     */
    observeArray(items: Array<any>) {
        for (let i = 0; i < items.length; i++) {
            // observe(items[i]);
        }
    }
}

/**
 * 定义响应式对象
 */
export function defineReactive(
    obj: Object,
    key: string,
    val: any,
    customSetter?: ?Function,
    // shallow?: boolean
) {
    const dep = new Dep();

    const property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable.configurable === false) {
        return;
    }

    const getter = property && property.get;
    const setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }

    // let childOb = !shallow && observe(val);

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val;
            console.log("[GET]监听数据: ", value);
            if (Dep.target) {
                dep.depend();
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            const value = getter ? getter.apply.call(obj) : val;
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return;
            }

            if (process.env.NODE_ENV !== "production" && customSetter) {
                customSetter();
            }

            if (getter && !setter) return;

            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }

            console.log("[SET]监听数据: ", value, "--", newVal);

            // childOb  !shallow && observe(newVal);
            dep.notify();
        }
    })
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
    if (!isObject(value)) {
      return
    }
    let ob: Observer | void
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      ob = value.__ob__
    } else if (
      shouldObserve &&
      !isServerRendering() &&
      (Array.isArray(value) || isPlainObject(value)) &&
      Object.isExtensible(value) &&
      !value._isVue
    ) {
      ob = new Observer(value)
    }
    if (asRootData && ob) {
      ob.vmCount++
    }
    return ob
  }

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(target: Array<any> | Object, key: any, val: any): any {
    if (process.env.NODE_ENV !== 'production' &&
        (isUndef(target) || isPrimitive(target))
    ) {
        warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
    }
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key)
        target.splice(key, 1, val)
        return val
    }
    if (key in target && !(key in Object.prototype)) {
        target[key] = val
        return val
    }
    const ob = (target: any).__ob__
    if (target._isVue || (ob && ob.vmCount)) {
        process.env.NODE_ENV !== 'production' && warn(
            'Avoid adding reactive properties to a Vue instance or its root $data ' +
            'at runtime - declare it upfront in the data option.'
        )
        return val
    }
    if (!ob) {
        target[key] = val
        return val
    }
    defineReactive(ob.value, key, val)
    ob.dep.notify()
    return val
}